/*****************************************************************
|
|    AP4 - Atom Based Sample Tables
|
|    Copyright 2003 Gilles Boccon-Gibod & Julien Boeuf
|
|
|    This atom is part of AP4 (MP4 Audio Proatom Library).
|
|    Unless you have obtained Bento4 under a difference license,
|    this version of Bento4 is Bento4|GPL.
|    Bento4|GPL is free software; you can redistribute it and/or modify
|    it under the terms of the GNU General Public License as published by
|    the Free Software Foundation; either version 2, or (at your option)
|    any later version.
|
|    Bento4|GPL is distributed in the hope that it will be useful,
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
|    GNU General Public License for more details.
|
|    You should have received a copy of the GNU General Public License
|    along with Bento4|GPL; see the atom COPYING.  If not, write to the
|    Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|    02111-1307, USA.
|
 ****************************************************************/

/*----------------------------------------------------------------------
|       includes
+---------------------------------------------------------------------*/
#include "Ap4.h"
#include "Ap4AtomSampleTable.h"
#include "Ap4ByteStream.h"
#include "Ap4StsdAtom.h"
#include "Ap4StscAtom.h"
#include "Ap4StcoAtom.h"
#include "Ap4Co64Atom.h"
#include "Ap4StszAtom.h"
#include "Ap4SttsAtom.h"
#include "Ap4CttsAtom.h"
#include "Ap4StssAtom.h"
#include "Ap4Sample.h"
#include "Ap4SampleEntry.h"
#include "Ap4Atom.h"
#include "Ap4Utils.h"

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::AP4_AtomSampleTable
+---------------------------------------------------------------------*/
AP4_AtomSampleTable::AP4_AtomSampleTable(AP4_ContainerAtom* stbl,
                                         AP4_ByteStream&    sample_stream) :
    m_SampleStream(sample_stream)
    , m_tsDelay(0)
{
    m_StscAtom = dynamic_cast<AP4_StscAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STSC));
    m_StcoAtom = dynamic_cast<AP4_StcoAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STCO));
    m_Co64Atom = dynamic_cast<AP4_Co64Atom*>(stbl->GetChild(AP4_ATOM_TYPE_CO64));
    m_StszAtom = dynamic_cast<AP4_StszAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STSZ));
    m_CttsAtom = dynamic_cast<AP4_CttsAtom*>(stbl->GetChild(AP4_ATOM_TYPE_CTTS));
    m_SttsAtom = dynamic_cast<AP4_SttsAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STTS));
    m_StssAtom = dynamic_cast<AP4_StssAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STSS));
    m_StsdAtom = dynamic_cast<AP4_StsdAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STSD));

    // keep a reference to the sample stream
    m_SampleStream.AddReference();

    if (m_StsdAtom && m_StszAtom && m_StscAtom && m_SttsAtom
        && (m_StszAtom->m_SampleSize == 1
            || (m_SttsAtom->m_Entries.ItemCount() == 1 && m_SttsAtom->m_Entries[0].m_SampleDuration == 1)))
    {
        // fix mov files
        for (AP4_List<AP4_Atom>::Item* item = m_StsdAtom->GetChildren().FirstItem();
            item;
            item = item->GetNext()) {
            AP4_Atom* atom = item->GetData();

            if (atom->GetType() == AP4_ATOM_TYPE_MP4A) {
                // ignore corrupted Vorbis (I don't know why the 'mp4a' type is here)
                AP4_ASSERT(0);
                break;
            }

            if (AP4_AudioSampleEntry* ase = dynamic_cast<AP4_AudioSampleEntry*>(atom)) {
                AP4_UI32 SamplesPerPacket = ase->GetSamplesPerPacket();
                AP4_UI32 BytesPerFrame = ase->GetBytesPerFrame();

                if (SamplesPerPacket > 0 && BytesPerFrame > 0) {
                    for(int i = 0, j = m_StscAtom->m_Entries.ItemCount(); i < j; i++) {
                        AP4_StscTableEntry& e = m_StscAtom->m_Entries[i];
                        AP4_ASSERT(e.m_SamplesPerChunk % SamplesPerPacket == 0);
                        e.m_SamplesPerChunk = e.m_SamplesPerChunk / SamplesPerPacket;
                        e.m_FirstSample = (e.m_FirstSample - 1) / SamplesPerPacket + 1;
                    }

                    AP4_ASSERT(m_StszAtom->m_SampleCount % SamplesPerPacket == 0);
                    m_StszAtom->m_SampleCount = m_StszAtom->m_SampleCount / SamplesPerPacket;
                    m_StszAtom->m_SampleSize = BytesPerFrame;

                    AP4_ASSERT(m_SttsAtom->m_Entries.ItemCount() == 1);
                    m_SttsAtom->m_Entries[0].m_SampleCount = m_StszAtom->m_SampleCount;
                    m_SttsAtom->m_Entries[0].m_SampleDuration = SamplesPerPacket;
                }
            }
        }
    }
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::~AP4_AtomSampleTable
+---------------------------------------------------------------------*/
AP4_AtomSampleTable::~AP4_AtomSampleTable()
{
    m_SampleStream.Release();
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::GetSample
+---------------------------------------------------------------------*/
AP4_Result
AP4_AtomSampleTable::GetSample(AP4_Ordinal index,
                               AP4_Sample& sample)
{
    AP4_Result result;

    // check that we have a chunk offset table
    if (m_StcoAtom == NULL && m_Co64Atom == NULL) {
        return AP4_ERROR_INVALID_FORMAT;
    }

    // MP4 uses 1-based indexes internally, so adjust by one
    index++;

    // find out in which chunk this sample is located
    AP4_Ordinal chunk, skip, desc;
    result = m_StscAtom->GetChunkForSample(index, chunk, skip, desc);
    if (AP4_FAILED(result)) return result;

    // check that the result is within bounds
    if (skip > index) return AP4_ERROR_INTERNAL;

    // get the atom offset for this chunk
    AP4_Offset offset;
    if (m_StcoAtom) result = m_StcoAtom->GetChunkOffset(chunk, offset);
    else if (m_Co64Atom) result = m_Co64Atom->GetChunkOffset(chunk, offset);
    else result = AP4_ERROR_INTERNAL;
    if (AP4_FAILED(result)) return result;
/*
    // compute the additional offset inside the chunk
    for (unsigned int i = index-skip; i < index; i++) {
        AP4_Size size;
        result = m_StszAtom->GetSampleSize(i, size);
        if (AP4_FAILED(result)) return result;
        offset += size;
    }
*/
    AP4_Size size;
    result = m_StszAtom->GetSampleSize(index - skip, index, size);
    if (AP4_FAILED(result)) return result;
    offset += size;

    // set the description index
    sample.SetDescriptionIndex(desc-1); // adjust for 0-based indexes

    // set the dts and cts
    AP4_TimeStamp dts;
    AP4_Duration duration;
    result = m_SttsAtom->GetDts(index, dts, duration);
    if (AP4_FAILED(result)) return result;
    sample.SetDts(dts);
    sample.SetDuration(duration);

    AP4_SI32 cts_offset = 0;
    if (m_CttsAtom) {
        m_CttsAtom->GetCtsOffset(index, cts_offset);
    }
    sample.SetCts(dts + cts_offset + m_tsDelay);

    // set the size
    AP4_Size sample_size;
    result = m_StszAtom->GetSampleSize(index, sample_size);
    if (AP4_FAILED(result)) return result;
    sample.SetSize(sample_size);

    // set the sync flag
    if (m_StssAtom == NULL) {
        sample.SetSync(true);
    } else {
        sample.SetSync(m_StssAtom->IsSampleSync(index));
    }

    // set the offset
    sample.SetOffset(offset);

    // set the data stream
    sample.SetDataStream(m_SampleStream);

    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::GetSampleCount
+---------------------------------------------------------------------*/
AP4_Cardinal
AP4_AtomSampleTable::GetSampleCount()
{
    return m_StszAtom ? m_StszAtom->GetSampleCount() : 0;
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::GetSampleDescription
+---------------------------------------------------------------------*/
AP4_SampleDescription*
AP4_AtomSampleTable::GetSampleDescription(AP4_Ordinal index)
{
    return m_StsdAtom ? m_StsdAtom->GetSampleDescription(index) : NULL;
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::GetSampleDescriptionCount
+---------------------------------------------------------------------*/
AP4_Cardinal
AP4_AtomSampleTable::GetSampleDescriptionCount()
{
    return m_StsdAtom ? m_StsdAtom->GetSampleDescriptionCount() : 0;
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::GetChunkForSample
+---------------------------------------------------------------------*/
AP4_Result
AP4_AtomSampleTable::GetChunkForSample(AP4_Ordinal  sample,
                                       AP4_Ordinal& chunk,
                                       AP4_Ordinal& skip,
                                       AP4_Ordinal& sample_description)
{
    return m_StscAtom ? m_StscAtom->GetChunkForSample(sample, chunk, skip, sample_description) : AP4_FAILURE;
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::GetChunkOffset
+---------------------------------------------------------------------*/
AP4_Result
AP4_AtomSampleTable::GetChunkOffset(AP4_Ordinal chunk, AP4_Offset& offset)
{
    return
        m_StcoAtom ? m_StcoAtom->GetChunkOffset(chunk, offset) :
        m_Co64Atom ? m_Co64Atom->GetChunkOffset(chunk, offset) :
        AP4_FAILURE;
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::SetChunkOffset
+---------------------------------------------------------------------*/
AP4_Result
AP4_AtomSampleTable::SetChunkOffset(AP4_Ordinal chunk, AP4_Offset offset)
{
    return
        m_StcoAtom ? m_StcoAtom->SetChunkOffset(chunk, offset) :
        m_Co64Atom ? m_Co64Atom->SetChunkOffset(chunk, offset) :
        AP4_FAILURE;
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::SetSampleSize
+---------------------------------------------------------------------*/
AP4_Result
AP4_AtomSampleTable::SetSampleSize(AP4_Ordinal sample, AP4_Size size)
{
    return m_StszAtom ? m_StszAtom->SetSampleSize(sample, size) : AP4_FAILURE;
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::GetSampleIndexForTimeStamp
+---------------------------------------------------------------------*/
AP4_Result
AP4_AtomSampleTable::GetSampleIndexForTimeStamp(AP4_TimeStamp ts,
                                                AP4_Ordinal& index)
{
    AP4_Result result = m_SttsAtom ? m_SttsAtom->GetSampleIndexForTimeStamp(ts, m_tsDelay, index) : AP4_FAILURE;
    if (AP4_SUCCEEDED(result)) {
        auto GetCts = [&](AP4_Ordinal i, AP4_SI64& cts) {
            AP4_TimeStamp dts;
            AP4_Duration duration;
            const AP4_Ordinal index = i + 1;
            result = m_SttsAtom->GetDts(index, dts, duration);
            if (AP4_FAILED(result)) {
                return result;
            }
            AP4_SI32 cts_offset = 0;
            if (m_CttsAtom) {
                m_CttsAtom->GetCtsOffset(index, cts_offset);
            }
            cts = dts + cts_offset + m_tsDelay;

            return AP4_SUCCESS;
        };

        AP4_SI64 cts;
        result = GetCts(index, cts);
        if (AP4_FAILED(result)) {
            return result;
        }

        const bool validStssAtom = m_StssAtom && m_StssAtom->GetEntries().ItemCount();
        const auto searched_ts = static_cast<AP4_SI64>(ts);

        if (cts == searched_ts
                && (!validStssAtom || m_StssAtom->IsSampleSync(index + 1))) {
            return AP4_SUCCESS;
        }

        for (;;) {
            if (index > 0 && cts >= searched_ts) {
                for (AP4_Ordinal i = index - 1; i > 0; i--) {
                    if (validStssAtom && !m_StssAtom->IsSampleSync(i + 1)) {
                        continue;
                    }

                    result = GetCts(i, cts);
                    if (AP4_FAILED(result)) {
                        return result;
                    }

                    if (cts <= searched_ts) {
                        index = i;
                        return AP4_SUCCESS;
                    }
                }

                break;
            } else if (index < GetSampleCount() && cts < searched_ts) {
                for (AP4_Ordinal i = index + 1; i < GetSampleCount(); i++) {
                    if (validStssAtom && !m_StssAtom->IsSampleSync(i + 1)) {
                        continue;
                    }

                    result = GetCts(i, cts);
                    if (AP4_FAILED(result)) {
                        return result;
                    }

                    if (cts > searched_ts) {
                        if (!validStssAtom) {
                            index = i > 0 ? i - 1 : 0;
                            return AP4_SUCCESS;
                        }
                        index = i;
                        break;
                    }
                }
            }

            if (!validStssAtom) {
                break;
            }
        }

        return AP4_SUCCESS;
    }
    return result;
}

/*----------------------------------------------------------------------
|       AP4_AtomSampleTable::SetTimeDelay
+---------------------------------------------------------------------*/
AP4_Result
AP4_AtomSampleTable::SetTimeDelay(AP4_SI64 tsDelay)
{
    m_tsDelay = tsDelay;
    return AP4_SUCCESS;
}
